import polars as pl
import tkinter as tk
from tkinter import filedialog, messagebox, scrolledtext
import threading

# 全局变量用于存储文件路径
picking_file_path = None
inventory_file_path = None
sub_file_path = None
# 全局变量用于存储用户选择的模式
allocation_mode = None  # 默认不选择任何模式


def set_allocation_mode(mode):
    """设置分配模式"""
    global allocation_mode
    allocation_mode = mode


def allocate_inventory(picking_file=None, inventory_file=None):
    """
    根据捡料单和库存数据，按照指定优先级分配库存
    """
    try:
        # 根据分配模式设置列名和重命名映射
        if allocation_mode == "domestic":
            # 国内模式
            columns = ['入库子库', '物料', '数量/单位']
            rename_map = {'入库子库': 'sub', '物料': 'item', '数量/单位': 'qty'}
        else:
            # 海外模式
            columns = ['出库子库', '需求编码', '数量']
            rename_map = {'出库子库': 'sub', '需求编码': 'item', '数量': 'qty'}

        # 读取并处理捡料单数据，只读取需要的列并指定数据类型
        p_df = (
            pl.read_excel(picking_file, sheet_name='Sheet1',
                          read_options={'use_columns': columns},
                          schema_overrides={columns[0]: pl.String, columns[1]: pl.String, columns[2]: pl.Int32}
                          )
            # 重命名列名为英文以便后续处理
            .rename(rename_map)
            # 按子库和物料分组，汇总需求数量
            .group_by(['sub', 'item'])
            .agg(pl.col('qty').sum().alias('qty'))
        )

        # 读取子库对应表数据
        sub_df = (
            pl.read_excel(sub_file_path, sheet_name='Sheet1',
                          read_options={'use_columns': ['ICARE子库', 'ERP货位']},
                          schema_overrides={'ICARE子库': pl.String, 'ERP货位': pl.String}
                          )
            # 重命名列名为英文以便后续处理
            .rename({'ICARE子库': 'sub', 'ERP货位': 'erp_sub'})
        )

        # 读取并处理库存数据，只读取需要的列并指定数据类型
        kc_df = (
            pl.read_excel(inventory_file, sheet_name='Sheet1',
                          read_options={'use_columns': ['ITEM_CODE', 'LOCATOR_CODE', 'ERP_LOCATOR_CODE','LPN_NAME', 'ONHAND_QUANTITY']},
                          schema_overrides={'ITEM_CODE': pl.String, 'LOCATOR_CODE': pl.String, 'ERP_LOCATOR_CODE': pl.String,
                                            'LPN_NAME':pl.String,'ONHAND_QUANTITY': pl.Int32}
                          )
            # 重命名列名为英文以便后续处理
            .rename({'ERP_LOCATOR_CODE': 'sub', 'ITEM_CODE': 'item', 'LOCATOR_CODE': 'hw','LPN_NAME':'lpn', 'ONHAND_QUANTITY': 'qty'})
            # 按子库、物料和货位分组，汇总可用数量
            .group_by(['sub', 'item', 'hw','lpn'])
            .agg(
                pl.col('qty').sum().alias('qty')
            )
            # 从货位编码中提取库区信息（取第2个字符）
            .with_columns(
                pl.col('hw').str.slice(1, 1).alias('kq')
            )
        )

        # 根据子库对应表更新捡料单中的子库
        # 如果p_df中的sub在sub_df中存在，则用sub_df中的erp_sub替换p_df中的sub
        p_df = p_df.join(sub_df, on='sub', how='left').with_columns(
            pl.when(pl.col('erp_sub').is_not_null())
            .then(pl.col('erp_sub'))
            .otherwise(pl.col('sub'))
            .alias('sub')
        ).drop('erp_sub')

        # 标准化子库字段以便匹配（将星号替换为连字符）
        # 这是因为捡料单中的子库使用星号分隔，而库存数据使用连字符分隔
        p_df = p_df.with_columns(
            pl.col('sub').str.replace_all('*', '-', literal=True).alias('normalized_sub')
        )

        kc_df = kc_df.with_columns(
            pl.col('sub').str.replace_all('*', '-', literal=True).alias('normalized_sub')
        )

        # 初始化结果列表，用于存储分配结果
        result_list = []

        # 将库存数据转换为可以修改的字典列表，便于实时更新库存数量
        kc_list = kc_df.to_dicts()

        # 处理每一行捡料单
        for row in p_df.iter_rows(named=True):
            sub = row['sub']
            normalized_sub = row['normalized_sub']
            item = row['item']
            required_qty = row['qty']

            # 找到匹配的库存记录（使用标准化的子库字段）
            # 筛选条件：子库匹配、物料匹配、可用数量大于0
            matching_inventory = [inv for inv in kc_list if
                                  inv['normalized_sub'] == normalized_sub and inv['item'] == item and inv['qty'] > 0]

            if not matching_inventory:
                # 没有匹配的库存记录，记录缺口信息
                result_list.append({
                    'sub': sub,
                    'item': item,
                    'hw': None,  # 无货位分配
                    'lpn': None,
                    'pick_qty': 0,  # 分配数量为0
                    'lack_qty': required_qty  # 全部为缺口
                })
                continue

            # 按优先级处理库存分配

            # 第一优先级：查找单个货位满足需求
            # 遍历匹配的库存记录，找到第一个可用数量满足需求的货位
            priority1_match = None
            for inv in matching_inventory:
                if inv['qty'] >= required_qty:
                    priority1_match = inv
                    break

            # 如果找到满足条件的单个货位
            if priority1_match:
                result_list.append({
                    'sub': sub,
                    'item': item,
                    'hw': priority1_match['hw'],  # 分配的货位
                    'lpn': priority1_match['lpn'],  # 分配的lpn
                    'pick_qty': required_qty,  # 分配全部需求数量
                    'lack_qty': 0  # 无缺口
                })
                # 更新库存：减少该货位的可用数量
                priority1_match['qty'] -= required_qty
                continue  # 处理下一条捡料单

            # 第二优先级：同一库区的多个货位总和满足需求
            priority2_match = False
            # 按库区分组库存记录
            kq_groups = {}  # 用于存储按库区分组的库存记录
            # 遍历匹配的库存记录，将其按库区分组
            for inv in matching_inventory:
                kq = inv['kq']
                if kq not in kq_groups:  # 如果库区不存在，则创建一个新的列表
                    kq_groups[kq] = []
                kq_groups[kq].append(inv)  # 将该库存记录添加到对应的库区列表中

            # 遍历每个库区，检查该库区的总可用数量是否满足需求
            for kq, inv_list in kq_groups.items():
                total_qty = sum(inv['qty'] for inv in inv_list)
                if total_qty >= required_qty:
                    # 在该库区中分配库存
                    remaining_qty = required_qty  # 剩余需求数量
                    # 按顺序分配该库区的货位，直到满足需求
                    for inv in inv_list:
                        if remaining_qty <= 0:
                            break
                        # 分配数量为该货位可用数量和剩余需求数量的较小值
                        pick_qty = min(inv['qty'], remaining_qty)
                        result_list.append({
                            'sub': sub,
                            'item': item,
                            'hw': inv['hw'],  # 分配的货位
                            'lpn': inv['lpn'],  # 分配的lpn
                            'pick_qty': pick_qty,  # 分配的数量
                            'lack_qty': 0  # 无缺口
                        })
                        # 更新库存：减少该货位的可用数量
                        inv['qty'] -= pick_qty
                        # 更新剩余需求数量
                        remaining_qty -= pick_qty
                    priority2_match = True
                    break

            # 如果已在同一库区完成分配
            if priority2_match:
                continue  # 处理下一条捡料单

            # 第三优先级：跨库区的货位总和满足需求
            # 计算所有匹配库存记录的总可用数量
            total_available_qty = sum(inv['qty'] for inv in matching_inventory)
            # 如果总可用数量满足需求
            if total_available_qty >= required_qty:
                # 按可用量降序排列，优先使用数量多的货位
                sorted_inventory = sorted(matching_inventory, key=lambda x: x['qty'], reverse=True)
                remaining_qty = required_qty  # 剩余需求数量
                # 按顺序分配货位，直到满足需求
                for inv in sorted_inventory:
                    if remaining_qty <= 0:
                        break
                    # 分配数量为该货位可用数量和剩余需求数量的较小值
                    pick_qty = min(inv['qty'], remaining_qty)
                    result_list.append({
                        'sub': sub,
                        'item': item,
                        'hw': inv['hw'],  # 分配的货位
                        'lpn': inv['lpn'],  # 分配的lpn
                        'pick_qty': pick_qty,  # 分配的数量
                        'lack_qty': 0  # 无缺口
                    })
                    # 更新库存：减少该货位的可用数量
                    inv['qty'] -= pick_qty
                    # 更新剩余需求数量
                    remaining_qty -= pick_qty
                continue  # 处理下一条捡料单

            # 第四优先级：总可用量不足，分配所有能分配的数量
            remaining_qty = required_qty  # 剩余需求数量（即缺口数量）
            # 分配所有可用的库存
            for inv in matching_inventory:
                if remaining_qty <= 0:
                    break
                if inv['qty'] > 0:
                    pick_qty = inv['qty']  # 分配该货位的全部可用量
                    result_list.append({
                        'sub': sub,
                        'item': item,
                        'hw': inv['hw'],  # 分配的货位
                        'lpn': inv['lpn'],  # 分配的lpn
                        'pick_qty': pick_qty,  # 分配的数量
                        'lack_qty': 0  # 无缺口（这部分已分配）
                    })
                    # 更新库存：清空该货位的可用数量
                    inv['qty'] -= pick_qty
                    # 更新剩余需求数量（即缺口数量）
                    remaining_qty -= pick_qty

            # 记录缺口数量
            if remaining_qty > 0:
                result_list.append({
                    'sub': sub,
                    'item': item,
                    'hw': None,  # 无货位分配
                    'lpn': None,  # 无lpn
                    'pick_qty': 0,  # 无分配数量
                    'lack_qty': remaining_qty  # 记录缺口数量
                })

        # 转换结果为DataFrame，确保所有字段都有正确的类型
        result_df = pl.DataFrame({
            'sub': [r['sub'] for r in result_list],
            'item': [r['item'] for r in result_list],
            'hw': [r['hw'] for r in result_list],
            'lpn': [r['lpn'] for r in result_list],
            'pick_qty': [r['pick_qty'] for r in result_list],
            'lack_qty': [r['lack_qty'] for r in result_list]
        }, schema={
            'sub': pl.String,
            'item': pl.String,
            'hw': pl.String,
            'lpn': pl.String,
            'pick_qty': pl.Int32,
            'lack_qty': pl.Int32
        })

        # 保存结果到Excel文件
        result_df.write_excel("分配结果.xlsx")

        # 导出更新后的实时库存
        # 将更新后的库存列表转换为DataFrame
        updated_inventory_df = pl.DataFrame(kc_list)
        # 选择需要的字段并重命名
        updated_inventory_df = updated_inventory_df.select([
            pl.col('sub').alias('ERP_LOCATOR_CODE'),
            pl.col('item').alias('ITEM_CODE'),
            pl.col('hw').alias('LOCATOR_CODE'),
            pl.col('lpn').alias('LPN_NAME'),
            pl.col('qty').alias('ONHAND_QUANTITY')
        ])
        # 保存更新后的库存到Excel文件
        updated_inventory_df.write_excel("更新后库存.xlsx")

        # 统计结果
        total_records = len(result_df)  # 总记录数
        # 完全满足的记录数（无缺口）
        fulfilled_records = len(result_df.filter(pl.col('lack_qty') == 0))
        # 部分满足的记录数（有分配但仍有缺口）
        partial_records = len(result_df.filter((pl.col('lack_qty') > 0) & (pl.col('pick_qty') > 0)))
        # 完全未满足的记录数（无分配）
        unfulfilled_records = len(result_df.filter((pl.col('lack_qty') > 0) & (pl.col('pick_qty') == 0)))

        # 返回统计结果
        return {
            'total': total_records,
            'fulfilled': fulfilled_records,
            'partial': partial_records,
            'unfulfilled': unfulfilled_records
        }
    except Exception as e:
        raise e


def select_picking_file():
    """选择捡料单文件"""
    global picking_file_path
    file_path = filedialog.askopenfilename(
        title="选择捡料单文件",
        filetypes=[("Excel files", "*.xlsx"), ("All files", "*.*")]
    )
    if file_path:
        picking_file_path = file_path
        picking_file_label.config(text=f"捡料单文件: {file_path}")


def select_inventory_file():
    """选择库存文件"""
    global inventory_file_path
    file_path = filedialog.askopenfilename(
        title="选择库存文件",
        filetypes=[("Excel files", "*.xlsx"), ("All files", "*.*")]
    )
    if file_path:
        inventory_file_path = file_path
        inventory_file_label.config(text=f"库存文件: {file_path}")


def select_sub_file():
    """选择子库对应表文件"""
    global sub_file_path
    file_path = filedialog.askopenfilename(
        title="选择子库对应表文件",
        filetypes=[("Excel files", "*.xlsx"), ("All files", "*.*")]
    )
    if file_path:
        sub_file_path = file_path
        sub_file_label.config(text=f"子库对应表文件: {file_path}")


def start_allocation():
    """开始分配按钮的回调函数"""
    # 在新线程中运行分配逻辑，避免阻塞GUI
    allocation_thread = threading.Thread(target=run_allocation)
    allocation_thread.start()


def run_allocation():
    """在新线程中运行分配逻辑"""
    try:
        # 检查是否选择了分配模式
        if allocation_mode is None:
            messagebox.showerror("错误", "请先选择分配模式（国内或海外）")
            start_button.config(state=tk.NORMAL)
            return

        # 禁用开始分配按钮，防止重复点击
        start_button.config(state=tk.DISABLED)
        result_text.delete(1.0, tk.END)
        result_text.insert(tk.END, "正在处理中，请稍候...\n")
        root.update()

        # 执行分配逻辑
        stats = allocate_inventory(picking_file_path, inventory_file_path)

        # 显示结果
        result_text.delete(1.0, tk.END)
        result_text.insert(tk.END, "分配完成！\n\n")
        result_text.insert(tk.END, f"总记录数: {stats['total']}\n")
        result_text.insert(tk.END, f"完全满足的记录数: {stats['fulfilled']}\n")
        result_text.insert(tk.END, f"部分满足的记录数: {stats['partial']}\n")
        result_text.insert(tk.END, f"完全未满足的记录数: {stats['unfulfilled']}\n\n")
        result_text.insert(tk.END, "结果已保存到'分配结果.xlsx'")

        # 显示成功消息框
        messagebox.showinfo("完成", "分配完成！结果已保存到'分配结果.xlsx'")
    except Exception as e:
        result_text.delete(1.0, tk.END)
        result_text.insert(tk.END, f"处理过程中出现错误:\n{str(e)}")
        messagebox.showerror("错误", f"处理过程中出现错误:\n{str(e)}")
    finally:
        # 重新启用开始分配按钮
        start_button.config(state=tk.NORMAL)


# 创建GUI窗口
root = tk.Tk()
root.title("库存分配系统")
root.geometry("600x500")

# 创建主框架
main_frame = tk.Frame(root)
main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)

# 创建模式选择框架
mode_frame = tk.LabelFrame(main_frame, text="分配模式")
mode_frame.pack(fill=tk.X, pady=5)

# 创建模式选择的单选按钮
mode_var = tk.StringVar()  # 不设置默认值

domestic_radio = tk.Radiobutton(mode_frame, text="国内", variable=mode_var, value="domestic", command=lambda: set_allocation_mode("domestic"))
domestic_radio.pack(side=tk.LEFT, padx=20)

overseas_radio = tk.Radiobutton(mode_frame, text="海外", variable=mode_var, value="overseas", command=lambda: set_allocation_mode("overseas"))
overseas_radio.pack(side=tk.LEFT, padx=20)

# 创建文件选择框架
file_frame = tk.LabelFrame(main_frame, text="文件选择")
file_frame.pack(fill=tk.X, pady=5)

# 捡料单文件选择
picking_file_button = tk.Button(file_frame, text="选择捡料单文件", command=select_picking_file)
picking_file_button.pack(side=tk.TOP, pady=5)

picking_file_label = tk.Label(file_frame, text="捡料单文件: 未选择")
picking_file_label.pack(side=tk.TOP, pady=5)

# 库存文件选择
inventory_file_button = tk.Button(file_frame, text="选择库存文件", command=select_inventory_file)
inventory_file_button.pack(side=tk.TOP, pady=5)

inventory_file_label = tk.Label(file_frame, text="库存文件: 未选择")
inventory_file_label.pack(side=tk.TOP, pady=5)

# 子库对应表文件选择
sub_file_button = tk.Button(file_frame, text="选择子库对应表文件", command=select_sub_file)
sub_file_button.pack(side=tk.TOP, pady=5)

sub_file_label = tk.Label(file_frame, text="子库对应表文件: 未选择")
sub_file_label.pack(side=tk.TOP, pady=5)

# 开始分配按钮
start_button = tk.Button(main_frame, text="开始分配", command=start_allocation, bg="lightblue",
                         font=("Arial", 12, "bold"))
start_button.pack(pady=10)

# 结果显示区域
result_frame = tk.LabelFrame(main_frame, text="处理结果")
result_frame.pack(fill=tk.BOTH, expand=True, pady=5)

result_text = scrolledtext.ScrolledText(result_frame, wrap=tk.WORD, width=60, height=10)
result_text.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)

# 运行分配逻辑
if __name__ == "__main__":
    # 如果直接运行脚本，则启动GUI
    root.mainloop()
